package pers.chao.logger.vo;

import pers.chao.tool.date.EasyDateFormat;
import pers.chao.tool.validate.ObjValidator;

import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Function;

/**
 * Description: 访问日志信息
 *
 * @author W.Chao
 * @date 2020/11/16 23:52
 **/
public class AccessLogInfo {
	/**
	 * 日志唯一id
	 */
	private String id;
	/**
	 * 模块说明
	 *
	 * @See AccessLog#module()
	 */
	private String module;
	/**
	 * 请求动作
	 */
	private String action;
	/**
	 * 方法或类的描述
	 *
	 * @See AccessLog#description()
	 */
	private String description;
	/**
	 * 访问的Java方法
	 */
	private Method method;
	/**
	 * 访问的Java类
	 */
	private Class target;
	/**
	 * 访问时Java方法的参数
	 */
	private Map<String, Object> parameters;
	/**
	 * 访问者的ip
	 */
	private String ip;
	/**
	 * 请求的url地址
	 */
	private String url;
	/**
	 * 请求的http头信息
	 */
	private Map<String, String> httpHeaders;
	/**
	 * 请求的http方法
	 */
	private String httpMethod;
	/**
	 * 响应信息
	 */
	private Object response;
	/**
	 * 请求时间戳
	 */
	private long requestTimestamp;
	/**
	 * 响应时间戳
	 */
	private long responseTimestamp;
	/**
	 * 异常信息
	 */
	private Throwable exception;

	// * * * * * * * * * * * * * * * * * * * * *
	// * 额外信息，可由监听者自行设置，默认为""    *
	// * * * * * * * * * * * * * * * * * * * * *

	/**
	 * 操作者id
	 */
	private String operatorId;
	/**
	 * 操作者帐户名
	 */
	private String operatorName;
	/**
	 * 操作者类型/等级
	 */
	private String operatorType;
	/**
	 * 其他信息
	 */
	private Map<String, String> additional;

	public void setAction(String action) {
		this.action = action;
	}

	public void setResponse(Object response) {
		this.response = response;
	}

	public void setResponseTimestamp(long responseTimestamp) {
		this.responseTimestamp = responseTimestamp;
	}

	public void setException(Throwable exception) {
		this.exception = exception;
	}

	public String getId() {
		return id;
	}

	public String getModule() {
		return module;
	}

	public String getAction() {
		return action;
	}

	public String getDescription() {
		return description;
	}

	public Method getMethod() {
		return method;
	}

	public Class getTarget() {
		return target;
	}

	public Map<String, Object> getParameters() {
		return parameters;
	}

	public String getIp() {
		return ip;
	}

	public String getUrl() {
		return url;
	}

	public Map<String, String> getHttpHeaders() {
		return httpHeaders;
	}

	public String getHttpMethod() {
		return httpMethod;
	}

	public Object getResponse() {
		return response;
	}

	public long getRequestTimestamp() {
		return requestTimestamp;
	}

	public long getResponseTimestamp() {
		return responseTimestamp;
	}

	public Throwable getException() {
		return exception;
	}

	public String getOperatorId() {
		return operatorId;
	}

	public void setOperatorId(String operatorId) {
		this.operatorId = operatorId;
	}

	public String getOperatorName() {
		return operatorName;
	}

	public void setOperatorName(String operatorName) {
		this.operatorName = operatorName;
	}

	public String getOperatorType() {
		return operatorType;
	}

	public void setOperatorType(String operatorType) {
		this.operatorType = operatorType;
	}

	public Map<String, String> getAdditional() {
		return additional;
	}

	public void setAdditional(Map<String, String> additional) {
		this.additional = additional;
	}

	public void putAdditionalInfo(String key, String value) {
		this.additional.put(key, value);
	}

	private AccessLogInfo() {
	}

	private AccessLogInfo(AccessLogInfoBuilder builder) {

		this.id = builder.id;
		this.module = builder.module;
		this.action = builder.action;
		this.description = builder.description;
		this.method = builder.method;
		this.target = builder.target;
		this.parameters = builder.parameters;
		this.ip = builder.ip;
		this.url = builder.url;
		this.httpHeaders = builder.httpHeaders;
		this.httpMethod = builder.httpMethod;
		this.response = builder.response;
		this.requestTimestamp = builder.requestTimestamp;
		this.responseTimestamp = builder.responseTimestamp;
		this.exception = builder.exception;
		this.operatorId = "";
		this.operatorName = "";
		this.operatorType = "";
		this.additional = new HashMap<>();
	}

	public static AccessLogInfoBuilder builder() {
		return new AccessLogInfoBuilder();
	}

	/**
	 * 更少的信息log
	 *
	 * @return
	 */
	public String liteLog() {
		Map<String, Object> logInfoMap = simpleLogMap();
		StringBuilder log = new StringBuilder();
		log.append("id: ").append(logInfoMap.get("id"))
				.append(", module: ").append(logInfoMap.get("module"))
				.append(", action: ").append(logInfoMap.get("action"))
				.append(", method: ").append(logInfoMap.get("method"))
				.append(", parameters: ").append(logInfoMap.get("parameters"))
				.append(", ip: ").append(logInfoMap.get("ip"))
				.append(", response: ").append(logInfoMap.get("response"))
				.append(", requestTimestamp: ").append(
				EasyDateFormat.format(new Date((Long) logInfoMap.get("requestTimestamp")), EasyDateFormat.defaultPattern()))
				.append(", responseTimestamp: ").append(
				EasyDateFormat.format(new Date((Long) logInfoMap.get("responseTimestamp")), EasyDateFormat.defaultPattern()));
		if (logInfoMap.containsKey("exception")) {
			log.append(", exception: ").append(logInfoMap.get("exception"));
		}
		return log.toString();
	}

	/**
	 * 将构造的信息打印为String
	 *
	 * @return
	 */
	public String simpleLog() {
		Map<String, Object> logInfoMap = simpleLogMap();
		StringBuilder log = new StringBuilder();
		log.append("id: ").append(logInfoMap.get("id"))
				.append(", module: ").append(logInfoMap.get("module"))
				.append(", action: ").append(logInfoMap.get("action"))
				.append(", describe: ").append(logInfoMap.get("describe"))
				.append(", method: ").append(logInfoMap.get("method"))
				.append(", target: ").append(logInfoMap.get("target"))
				.append(", parameters: ").append(logInfoMap.get("parameters"))
				.append(", ip: ").append(logInfoMap.get("ip"))
				.append(", url: ").append(logInfoMap.get("url"))
				.append(", httpHeaders: ").append(logInfoMap.get("httpHeaders"))
				.append(", httpMethod: ").append(logInfoMap.get("httpMethod"))
				.append(", response: ").append(logInfoMap.get("response"))
				.append(", requestTimestamp: ").append(
				EasyDateFormat.format(new Date((Long) logInfoMap.get("requestTimestamp")), EasyDateFormat.defaultPattern()))
				.append(", responseTimestamp: ").append(
				EasyDateFormat.format(new Date((Long) logInfoMap.get("responseTimestamp")), EasyDateFormat.defaultPattern()))
				.append(", useTime: ").append(logInfoMap.get("useTime"));
		if (logInfoMap.containsKey("operatorId")) {
			log.append(", operatorId: ").append(logInfoMap.get("operatorId"));
		}
		if (logInfoMap.containsKey("operatorName")) {
			log.append(", operatorName: ").append(logInfoMap.get("operatorName"));
		}
		if (logInfoMap.containsKey("operatorType")) {
			log.append(", operatorType: ").append(logInfoMap.get("operatorType"));
		}
		if (logInfoMap.containsKey("additional")) {
			log.append(", additional: ").append(logInfoMap.get("additional"));
		}
		if (logInfoMap.containsKey("exception")) {
			log.append(", exception: ").append(logInfoMap.get("exception"));
		}
		return log.toString();
	}

	/**
	 * 信息构造map
	 *
	 * @return
	 */
	public Map<String, Object> simpleLogMap() {
		Map<String, Object> logInfoMap = new HashMap<>();
		Function<Object, Serializable> objectFilter = (o) -> o != null ? o.toString() : null;
		logInfoMap.put("module", module);
		logInfoMap.put("action", action);
		logInfoMap.put("describe", description);
		if (method != null) {
			StringJoiner methodAppender = new StringJoiner(",", method.getName().concat("("), ")");
			String[] parameterNames = parameters.keySet().toArray(new String[0]);
			Class[] parameterTypes = method.getParameterTypes();

			for (int i = 0; i < parameterTypes.length; i++) {
				methodAppender.add(parameterTypes[i].getSimpleName().concat(" ").concat(parameterNames.length >= i ? parameterNames[i] : ("arg" + i)));
			}
			logInfoMap.put("method", methodAppender.toString());
		}
		logInfoMap.put("target", target != null ? target.getName() : "");
		Map<String, Object> newParameter = new LinkedHashMap<>(parameters);
		newParameter.entrySet().forEach(entry -> {
			if (entry.getValue() != null) {
				entry.setValue(objectFilter.apply(entry.getValue()));
			}
		});

		logInfoMap.put("parameters", newParameter);
		logInfoMap.put("httpHeaders", httpHeaders);
		logInfoMap.put("httpMethod", httpMethod);
		logInfoMap.put("ip", ip);
		logInfoMap.put("url", url);
		logInfoMap.put("response", objectFilter.apply(response));
		logInfoMap.put("requestTimestamp", requestTimestamp);
		logInfoMap.put("responseTimestamp", responseTimestamp);
		logInfoMap.put("id", id);
		logInfoMap.put("useTime", responseTimestamp - requestTimestamp);
		if (exception != null) {
			StringWriter writer = new StringWriter();
			exception.printStackTrace(new PrintWriter(writer));
			logInfoMap.put("exception", writer.toString());
		}
		if (ObjValidator.isNotEmptyString(operatorId)) {
			logInfoMap.put("operatorId", operatorId);
		}
		if (ObjValidator.isNotEmptyString(operatorName)) {
			logInfoMap.put("operatorName", operatorName);
		}
		if (ObjValidator.isNotEmptyString(operatorType)) {
			logInfoMap.put("operatorType", operatorType);
		}
		if (ObjValidator.isNotEmptyMap(additional)) {
			logInfoMap.put("additional", additional);
		}

		return logInfoMap;
	}

	public static class AccessLogInfoBuilder {
		private String id;
		private String module;
		private String action;
		private String description;
		private Method method;
		private Class target;
		private Map<String, Object> parameters;
		private String ip;
		private String url;
		private Map<String, String> httpHeaders;
		private String httpMethod;
		private Object response;
		private long requestTimestamp;
		private long responseTimestamp;
		private Throwable exception;

		public AccessLogInfoBuilder setId(String id) {
			this.id = id;
			return this;
		}

		public AccessLogInfoBuilder setModule(String module) {
			this.module = module;
			return this;
		}

		public AccessLogInfoBuilder setAction(String action) {
			this.action = action;
			return this;
		}

		public AccessLogInfoBuilder setDescription(String description) {
			this.description = description;
			return this;
		}

		public AccessLogInfoBuilder setMethod(Method method) {
			this.method = method;
			return this;
		}

		public AccessLogInfoBuilder setTarget(Class target) {
			this.target = target;
			return this;
		}

		public AccessLogInfoBuilder setParameters(Map<String, Object> parameters) {
			this.parameters = parameters;
			return this;
		}

		public AccessLogInfoBuilder setIp(String ip) {
			this.ip = ip;
			return this;
		}

		public AccessLogInfoBuilder setUrl(String url) {
			this.url = url;
			return this;
		}

		public AccessLogInfoBuilder setHttpHeaders(Map<String, String> httpHeaders) {
			this.httpHeaders = httpHeaders;
			return this;
		}

		public AccessLogInfoBuilder setHttpMethod(String httpMethod) {
			this.httpMethod = httpMethod;
			return this;
		}

		public AccessLogInfoBuilder setResponse(Object response) {
			this.response = response;
			return this;
		}

		public AccessLogInfoBuilder setRequestTimestamp(long requestTimestamp) {
			this.requestTimestamp = requestTimestamp;
			return this;
		}

		public AccessLogInfoBuilder setResponseTimestamp(long responseTimestamp) {
			this.responseTimestamp = responseTimestamp;
			return this;
		}

		public AccessLogInfoBuilder setException(Throwable exception) {
			this.exception = exception;
			return this;
		}

		public AccessLogInfo build() {
			return new AccessLogInfo(this);
		}
	}
}
